#include "assert.h"
#include "datasturcture.h"
#include "error.h"
#include "deffs.h"
#include "inode.h"
#include "vfs.h"
#include "lock.h"
#include "mmu.h"
#include "stdio.h"
#include "string.h"
#include "type.h"

// device info entry in vdev_list
typedef struct {
    const char* devName;
    inode_t* devNode;
    fs_t* fs;
    bool mountable;
    list_entry_t vdev_link;
} vfs_dev_t;

#define le2dev(le, member) \
    to_struct((le), vfs_dev_t, member)

static list_entry_t vdevList;
static semaphore_t vdevList_sem;

static inline void
lock_vdevList(void) {
    semaphore_acquire(&vdevList_sem);
}

static inline void
unlock_vdevList(void) {
    semaphore_release(&vdevList_sem);
}

void
vfs_devList_init(void) {
    list_init(&vdevList);
    semaphore_init(&vdevList_sem, 1);
}

void
vfs_cleanup(void) {
    if (!list_empty(&vdevList)) {
        lock_vdevList();
        {
            list_entry_t* list = &vdevList;
            for (list_entry_t* le = list_next(list); le != list; le = list_next(le)) {
                vfs_dev_t* vdev = le2dev(le, vdev_link);
                if (vdev->fs != NULL)
                    fsop_cleanup(vdev->fs);
            }
        }
        unlock_vdevList();
    }
}

/*
 * vfs_get_root - Given a device name (stdin, stdout, etc.), hand
 *                back an appropriate inode.
 */
int
vfs_getRoot(const char* devName, inode_t** node_store) {
    assert(devName != NULL);
    int ret = -E_NO_DEV;
    if (!list_empty(&vdevList)) {
        lock_vdevList();
        {
            list_entry_t* list = &vdevList;
            for (list_entry_t* le = list_next(list); le != list; le = list_next(le)) {
                vfs_dev_t* vdev = le2dev(le, vdev_link);
                if (strcmp(devName, vdev->devName) == 0) {
                    inode_t* found = NULL;
                    if (vdev->fs != NULL)
                        found = fsop_getRoot(vdev->fs);
                    else if (!vdev->mountable) {
                        vop_refInc(vdev->devNode);
                        found = vdev->devNode;
                    }
                    if (found) {
                        ret = 0;
                        *node_store = found;
                    } else
                        ret = -E_NA_DEV;
                    break;
                }
            }
        }
        unlock_vdevList();
    }
    return ret;
}

const char*
vfs_getDeviceName(fs_t* fs) {
    assert(fs != NULL);
    list_entry_t* list = &vdevList;
    for (list_entry_t* le = list_next(list); le != list; le = list_next(le)) {
        vfs_dev_t* vdev = le2dev(le, vdev_link);
        if (vdev->fs == fs)
            return vdev->devName;
    }
    return NULL;
}

static bool
checkDevNameConflict(const char* devName) {
    list_entry_t* list = &vdevList;
    for (list_entry_t* le = list_next(list); le != list; le = list_next(le)) {
        vfs_dev_t* vdev = le2dev(le, vdev_link);
        if (strcmp(vdev->devName, devName) == 0)
            return FALSE;
    }
    return TRUE;
}

static int
vfs_doAdd(const char* devName, inode_t* devNode, fs_t* fs, bool mountable) {
    assert(devName != NULL);
    assert((devNode == NULL && !mountable) || (devNode != NULL && check_inode_type(devNode, device)));
    if (strlen(devName) > FS_MAX_DNAME_LEN)
        return -E_TOO_BIG;

    int ret = -E_NO_MEM;
    char* s_devName = strdup(devName);
    if (s_devName == NULL)
        return ret;

    vfs_dev_t* vdev = kmalloc(sizeof(vfs_dev_t));
    if (vdev == NULL) {
        kmfree(s_devName);  // TODO kfree更改为不是free 4k page
        return ret;
    }

    ret = -E_EXISTS;
    lock_vdevList();
    if (!checkDevNameConflict(s_devName)) {
        unlock_vdevList();
        kmfree(vdev);
        kmfree(s_devName);
        return ret;
    }

    vdev->devName = s_devName;
    vdev->devNode = devNode;
    vdev->mountable = mountable;
    vdev->fs = fs;

    list_add(&vdevList, &(vdev->vdev_link));
    unlock_vdevList();
    return 0;
}

int
vfs_addFS(const char* devName, fs_t* fs) {
    return vfs_doAdd(devName, NULL, fs, 0);
}

int
vfs_addDevice(const char* devName, inode_t* devNode, bool mountable) {
    return vfs_doAdd(devName, devNode, NULL, mountable);
}

/**
 * @brief 寻找指定devName的设备是否挂载
 *
 * @param devName
 * @param vdev_store
 * @return int 0-已挂载, -E_NO_DEV-未挂载
 */
static int
findMount(const char* devName, vfs_dev_t** vdev_store) {
    assert(devName != NULL);
    list_entry_t* list = &vdevList;
    for (list_entry_t* le = list_next(list); le != list; le = list_next(le)) {
        vfs_dev_t* vdev = le2dev(le, vdev_link);
        if (vdev->mountable && strcmp(vdev->devName, devName) == 0) {
            *vdev_store = vdev;
            return 0;
        }
    }
    return -E_NO_DEV;
}

int
vfs_mount(const char* devName, int (*mountFunc)(device_t* dev, fs_t** fs_store)) {
    vfs_dev_t* vdev;
    lock_vdevList();
    int ret = findMount(devName, &vdev);
    if (ret != 0) {  //已挂载
        unlock_vdevList();
        return ret;
    }
    if (vdev->fs != NULL) {  //?
        ret = -E_BUSY;
        unlock_vdevList();
        return ret;
    }
    assert(vdev->devName != NULL && vdev->mountable);

    device_t* dev = vop_info(vdev->devNode, device);
    ret = mountFunc(dev, &(vdev->fs));
    if (ret == 0) {  //成功挂载
        assert(vdev->fs != NULL);
        printf("vfs: mount %s.\n", vdev->devName);  // TODO该用其他版本打印
    }
    unlock_vdevList();
    return ret;
}

int
vfs_unmount(const char* devName) {
    vfs_dev_t* vdev;
    lock_vdevList();

    int ret = findMount(devName, &vdev);
    if (ret != 0)  //已挂载
        goto out;
    if (vdev->fs == NULL) {  //?
        ret = -E_INVAL;
        goto out;
    }
    assert(vdev->devName != NULL && vdev->mountable);

    ret = fsop_sync(vdev->fs);
    if (ret != 0)
        goto out;
    ret = fsop_unmount(vdev->fs);
    if (ret == 0) {  //成功取消挂载
        vdev->fs = NULL;
        printf("vfs: unmount %s.\n", vdev->devName);
    }

out:
    unlock_vdevList();
    return ret;
}

int
vfs_unmountAll(void) {
    if (!list_empty(&vdevList)) {  // TODO 有必要吗
        lock_vdevList();
        {
            list_entry_t* list = &vdevList;
            for (list_entry_t* le = list_next(list); le != list; le = list_next(le)) {
                vfs_dev_t* vdev = le2dev(le, vdev_link);
                if (vdev->mountable && vdev->fs != NULL) {
                    int ret = fsop_sync(vdev->fs);
                    if (ret != 0) {
                        printf("vfs: warning sync faild for %s: %d.\n", vdev->devName, ret);
                        continue;
                    }
                    ret = fsop_unmount(vdev->fs);
                    if (ret != 0) {
                        printf("vfs: warning unmount failed for %s: %d.\n", vdev->devName, ret);
                        continue;
                    }
                    vdev->fs = NULL;
                    printf("vfs: unmount %s.\n", vdev->devName);
                }
            }
        }
        unlock_vdevList();
    }
    return 0;
}